/**************************************************************************************************
**                                                                                               **
**  文件名称:  dal_pp_man.c                                                                      **
**  版权所有:  CopyRight @ Xiamen Yaxon NetWork CO.LTD. 2017                                     **
**  文件描述:  实现pp参数存储管理                                                                **
**  ===========================================================================================  **
**  创建信息:  | 2017-3-30 | LEON | 创建本模块                                                   **
**  ===========================================================================================  **
**  修改信息:  单击此处添加....                                                                  **
**************************************************************************************************/
#include "dal_include.h"
#include "dal_flash_drv.h"
#include "dal_dbg_man.h"
#include "dal_pp_man.h"

/*************************************************************************************************/
/*                           定义配置参数                                                        */
/*************************************************************************************************/
#define MAXM_PIMAGE          ((PAGE_PARA_END - PAGE_PARA_BGN + 1) * SIZE_PER_PAGE)
#define SIZE_PIMAGE          (1 * 1024)                                        /* 实际镜像大小 */
#define FLAG_DVALID          0xAA                                              /* 数据有效标志 */
#define NEED_UPDATE          0x55                                              /* 数据需要更新 */
#define PHDL_MAXNUM          10                                                /* 最大允许的回调函数个数 */
#define PPSCAN_TIME          1                                                 /* 定时器扫描间隔 (单位: 秒) */
#define DELAY_COUNT          (60 * 10)                                         /* 延时存储的累计次数 */

/*************************************************************************************************/
/*                           参数头数据结构                                                      */
/*************************************************************************************************/
typedef struct {
    INT8U       flag;                                                          /* 标识字符 */
    INT8U       chksum;                                                        /* 校验码 */
    INT16U      size;                                                          /* 参数大小 */
} ITEMHEAD_T;

/*************************************************************************************************/
/*                           参数注册表数据结构                                                  */
/*************************************************************************************************/
typedef struct {
    INT16U        size;                                                        /* 参数字节数 */
    const void   *init;                                                        /* 初始化指针 */
} PPITEM_REG_T;

/*************************************************************************************************/
/*                           参数变化回调函数结构体                                              */
/*************************************************************************************************/
typedef struct {
    INT8U         status;                                                      /* 使用情况 */
    PPITEM_ID_E   paraid;                                                      /* 参数ID */
    void        (*inform)(INT16U);                                             /* 回调函数 */
} PPITEM_HDL_T;

/*************************************************************************************************/
/*                           镜像区整区校验字段定义[必须是4字节的倍数]                           */
/*************************************************************************************************/
typedef struct {
    INT8U         chksum_o;                                                    /* 原码校验和 */
    INT8U         chksum_n;                                                    /* 反码校验和 */
    INT8U         flag_fst;                                                    /* 第一个标志(FLAG_DVALID) */
    INT8U         flag_scd;                                                    /* 第二个标志(FLAG_DVALID) */
} PPIMG_CHK_T;

/*************************************************************************************************/
/*                           定义模块局部变量                                                    */
/*************************************************************************************************/
static INT8U         s_update_tmr;                                             /* 定时更新存储器定时器 */
static INT8U         s_update_flg;                                             /* 是否要更新数据的标志 */
static INT32U        s_delay_cont;                                             /* 延时存储的计数器 */

static PPITEM_HDL_T  s_ppitem_hdl[PHDL_MAXNUM];                                /* 参数变化回调函数控制块 */
static INT8U         s_ppitem_img[SIZE_PIMAGE + sizeof(PPIMG_CHK_T)];          /* 用于缓存数据的镜像区域 */

#ifdef  PPITEM_DEF
#undef  PPITEM_DEF
#endif

#define PPITEM_DEF(PPITEM_ID, PPITEM_LEN, PPITEM_INIT)                         {PPITEM_LEN, PPITEM_INIT},

static const PPITEM_REG_T s_ppitem_tbl[] = {
       #include "dal_pp_man.def"
};

/**************************************************************************************************
**  函数名称:  FindFreeRegTbl
**  功能描述:  获取空闲的参数回调注册表项目
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
static INT16U FindFreeRegTbl(void)
{
    INT8U i;
    
    for (i = 0; i < PHDL_MAXNUM; i++) {
         if ((s_ppitem_hdl[i].status != FLAG_DVALID) || (s_ppitem_hdl[i].inform == NULL)) {
            break;
         }
    }
    
    if (i >= PHDL_MAXNUM) {                                                    /* 没有找到空余的 */
        return 0xFF;
    } else {
        YX_MEMSET(&s_ppitem_hdl[i], 0, sizeof(PPITEM_HDL_T));
        return i;
    }
}

/**************************************************************************************************
**  函数名称:  PPItemChangHdl
**  功能描述:  PP参数变更回调函数
**  输入参数:  item: 参数ID
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
static void PPItemChangHdl(PPITEM_ID_E ppid)
{
    INT16U i;
    
    for (i = 0; i < PHDL_MAXNUM; i++) {                                        /* 从注册表中逐一查询 */
        if (s_ppitem_hdl[i].paraid == ppid) {                                  /* 寻找参数ID值一致的 */
            if (s_ppitem_hdl[i].status == FLAG_DVALID) {                       /* 参数注册表是否有效 */
                SYS_ASSERT(s_ppitem_hdl[i].inform != NULL, RETURN_VOID);
                (*s_ppitem_hdl[i].inform)(ppid);
            }
        }
    }
}

/**************************************************************************************************
**  函数名称:  GetItemAddr
**  功能描述:  获取指定项目在缓冲数据中的起始地址
**  输入参数:  无
**  输出参数:  无
**  返回参数:  在缓冲数据中的起始地址
**************************************************************************************************/
static INT8U *GetItemAddr(PPITEM_ID_E ppid)
{
    INT8U        *ptr;
    INT32U        size;
    PPITEM_ID_E   index;
    
    ptr = (INT8U *)s_ppitem_img;
    
    for (index = (PPITEM_ID_E)0; index < PPITEM_MAXNO; index++) {
        
        if (ppid == index) {                                                   /* 找到了就返回该地址 */
            break;
        }
        
        size = sizeof(ITEMHEAD_T) + s_ppitem_tbl[index].size;
        
        if ((size % 4) != 0) {                                                 /* 保证每个参数4个字节对齐 */
            size += 4 - (size % 4);
        }
        
        ptr += size;                                                           /* 进行地址增量偏移 */
    }
    
    return ptr;
}

/**************************************************************************************************
**  函数名称:  GetItemChksum
**  功能描述:  计算指定项目的校验字段
**  输入参数:  ptr: 待计算参数数据缓冲区，len: 待计算参数数据缓冲区长度
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
static INT8U GetItemChksum(INT8U *ptr, INT16U len)
{
    return (INT8U)(TOOL_GetChkSum_N(ptr, len) + len);
}

/**************************************************************************************************
**  函数名称:  CheckItemChksum
**  功能描述:  对指定项目的校验字段进行校验
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
static BOOLEAN CheckItemChksum(PPITEM_ID_E ppid)
{
    INT8U       *p_data;
    ITEMHEAD_T  *p_item;
    
    p_item = (ITEMHEAD_T *)GetItemAddr(ppid);
    p_data = (INT8U *)p_item + sizeof(ITEMHEAD_T);
    
    if (p_item->flag != FLAG_DVALID) {                                         /* 标志位错误 */
        return FALSE;
    }
    
    if (p_item->size != s_ppitem_tbl[ppid].size) {                             /* 数据长度错误 */
        return FALSE;
    }
    
    if (p_item->chksum != GetItemChksum(p_data, s_ppitem_tbl[ppid].size)) {    /* 校验和错误 */
        return FALSE;
    }
    
    return TRUE;
}

/**************************************************************************************************
**  函数名称:  UpdateImgChksum
**  功能描述:  更新整个存储区的校验字段
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
static void UpdateImgChksum(void)
{
    INT32U tlen;
    
    tlen = sizeof(s_ppitem_img);
    
    s_ppitem_img[tlen - 4] = TOOL_GetChkSum_O(s_ppitem_img, tlen - 4);
    s_ppitem_img[tlen - 3] = TOOL_GetChkSum_N(s_ppitem_img, tlen - 4);
    s_ppitem_img[tlen - 2] = FLAG_DVALID;
    s_ppitem_img[tlen - 1] = FLAG_DVALID;
}

/**************************************************************************************************
**  函数名称:  CheckImgChksum
**  功能描述:  对整个存储区的校验字段进行校验
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
static BOOLEAN CheckImgChksum(void)
{
    INT32U tlen;
    
    tlen = sizeof(s_ppitem_img);
    
    if (s_ppitem_img[tlen - 4] != TOOL_GetChkSum_O(s_ppitem_img, tlen - 4)) {
        return FALSE;
    }
    
    if (s_ppitem_img[tlen - 3] != TOOL_GetChkSum_N(s_ppitem_img, tlen - 4)) {
        return FALSE;
    }
    
    if (s_ppitem_img[tlen - 2] != FLAG_DVALID) {
        return FALSE;
    }
    
    if (s_ppitem_img[tlen - 1] != FLAG_DVALID) {
        return FALSE;
    }
    
    return TRUE;
}

/**************************************************************************************************
**  函数名称:  WriteImageToFlash
**  功能描述:  将镜像数据写入到FLASH扇区
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
static void WriteImageToFlash(void)
{
    INT32U   addr;
    
    UpdateImgChksum();                                                         /* 更新整个存储区的校验字段 */
    
    ENTER_CRITICAL();
    
    FLASH_Unlock();
    
    DAL_Flash_EraseSomePages(PAGE_PARA_BGN, PAGE_PARA_END);                    /* 记得要先擦除扇区 */
    
    addr = DAL_Flash_GetAddrByPage(PAGE_PARA_BGN, TRUE);
    
    DAL_Flash_WriteData(addr, (INT8U *)s_ppitem_img, sizeof(s_ppitem_img));
    
    FLASH_Lock();
    
    EXIT_CRITICAL();
    
    #if DEBUG_PPIT > 1
    Debug_SysPrint("WriteImageToFlash: ");
    Debug_PrintHex((INT8U *)s_ppitem_img, sizeof(s_ppitem_img));
    Debug_SysPrint("\r\n");
    #endif
}

/**************************************************************************************************
**  函数名称:  ReadImageFromFlash
**  功能描述:  从FLASH扇区中读取镜像数据
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
static void ReadImageFromFlash(void)
{
    INT32U   dlen, addr;
    
    ENTER_CRITICAL();
    
    addr = DAL_Flash_GetAddrByPage(PAGE_PARA_BGN, TRUE);
    dlen = DAL_Flash_ReadData(addr, (INT8U *)s_ppitem_img, sizeof(s_ppitem_img));
    
    if (dlen != sizeof(s_ppitem_img)) {                                        /* 读取长度无效，再试一次 */
        #if DEBUG_PPIT > 0
        Debug_SysPrint("ReadImageFromFlash: dlen err(%d/%d)! Retry now...\r\n", dlen, sizeof(s_ppitem_img));
        #endif
        dlen = DAL_Flash_ReadData(addr, (INT8U *)s_ppitem_img, sizeof(s_ppitem_img));
        SYS_ASSERT((dlen == sizeof(s_ppitem_img)), RETURN_VOID);
    }
    
    EXIT_CRITICAL();
    
    #if DEBUG_PPIT > 1
    Debug_SysPrint("ReadImageFromFlash: ");
    Debug_PrintHex((INT8U *)s_ppitem_img, sizeof(s_ppitem_img));
    Debug_SysPrint("\r\n");
    #endif
    
    if (CheckImgChksum() == FALSE) {                                           /* 整个参数区的数据校验失败 */
        #if DEBUG_PPIT > 0
        Debug_SysPrint("ReadImageFromFlash: PP_Image Checksum Fail!!! ResetAllPara!!!\r\n");
        #endif
        PP_ResetAllPara();
    }
}

/**************************************************************************************************
**  函数名称:  UpdataImageHdl
**  功能描述:  定时更新存储区的处理函数
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
static void UpdataImageHdl(void)
{
    if (s_update_flg != NEED_UPDATE) {                                         /* 没有数据需要更新 */
        return;
    }

    /* 说明: 镜片控制过程中不能更新FLASH存储，否则会出现卡顿现象。因此引入了信号量来进行互斥 */
    
    if (++s_delay_cont >= DELAY_COUNT) {                                       /* 计数器倒数完毕 */
        if (BSP_SysSignalUseful() == TRUE) {                                   /* 系统信号量空闲 */
            WriteImageToFlash();
            s_delay_cont = 0;
            s_update_flg = 0;
        }
    }
}

/**************************************************************************************************
**  函数名称:  PubPara_Init
**  功能描述:  模块初始化函数
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
void PubPara_Init(void)
{
    INT16U i, size, t_size;
    
    for (i = 0, t_size = 0; i < PPITEM_MAXNO; i++) {                           /* 计算所有参数所占用的字节数 */
        
        size = sizeof(ITEMHEAD_T) + s_ppitem_tbl[i].size;
        
        if ((size % 4) != 0) {
            size += 4 - (size % 4);
        }
        
        t_size += size;
    }
    
    SYS_ASSERT(t_size <= SIZE_PIMAGE, RETURN_VOID);                            /* 注册参数的总长度超出范围 */
    SYS_ASSERT(SIZE_PIMAGE < MAXM_PIMAGE, RETURN_VOID);                        /* 所使用的容量超出扇区范围 */
    
    YX_MEMSET(s_ppitem_hdl, 0, sizeof(s_ppitem_hdl));
    
    ReadImageFromFlash();
    
    for (i = 0; i < PPITEM_MAXNO; i++) {                                       /* 逐个判断参数内容是否有效 */
        if (CheckItemChksum((PPITEM_ID_E)i) == FALSE) {
            #if DEBUG_PPIT > 0
            Debug_SysPrint("No %.2d Item Data Check Fail. ResetAllPara!!!\r\n", i);
            #endif
            PP_ResetAllPara();
            break;
        }
    }
    
    s_update_tmr = OSL_CreateTimer(UpdataImageHdl);
    OSL_StartTimer(s_update_tmr, _SECOND, PPSCAN_TIME);
}

/**************************************************************************************************
**  函数名称:  PP_ReadItem
**  功能描述:  读取一个pp参数
**  输入参数:  nid: 待读取参数ID
**  输出参数:  ptr: 读到的参数内容
**  返回参数:  true: 读取参数成功，false: 读取参数失败
**************************************************************************************************/
BOOLEAN PP_ReadItem(PPITEM_ID_E ppid, void *ptr)
{
    INT8U       *p_data;
    ITEMHEAD_T  *p_item;
    
    SYS_ASSERT(ptr != 0, RETURN_FALSE);
    SYS_ASSERT(ppid < PPITEM_MAXNO, RETURN_FALSE);

    p_item = (ITEMHEAD_T *)GetItemAddr(ppid);                                  /* 获取参数区域的地址 */
    p_data = (INT8U *)p_item + sizeof(ITEMHEAD_T);                             /* 获取有效数据的地址 */
    
    if (CheckItemChksum(ppid) == TRUE) {
        YX_MEMCPY(ptr, p_data, s_ppitem_tbl[ppid].size);                       /* 返回保存的参数数据 */
        return TRUE;
    } else {
        if (s_ppitem_tbl[ppid].init != NULL) {                                 /* 存在初始化参数 */
            YX_MEMCPY(ptr, s_ppitem_tbl[ppid].init, s_ppitem_tbl[ppid].size);
            return TRUE;
        } else {
            YX_MEMSET(ptr, 0, s_ppitem_tbl[ppid].size);
            return FALSE;
        }
    }
}

/**************************************************************************************************
**  函数名称:  PP_WriteItem
**  功能描述:  写入一个pp参数
**  输入参数:  id: 待写入参数ID，ptr: 待写入参数数据，now: 是否立即写入
**  输出参数:  无
**  返回参数:  true: 写入参数成功，false: 写入参数失败
**************************************************************************************************/
BOOLEAN PP_WriteItem(PPITEM_ID_E ppid, void *ptr, BOOLEAN now)
{
    INT8U       *p_data;
    ITEMHEAD_T  *p_item;
    
    SYS_ASSERT(ppid < PPITEM_MAXNO, RETURN_FALSE);
    
    p_item = (ITEMHEAD_T *)GetItemAddr(ppid);                                  /* 获取参数区域的地址 */
    p_data = (INT8U *)p_item + sizeof(ITEMHEAD_T);                             /* 获取有效数据的地址 */
    
    YX_MEMCPY(p_data, ptr, s_ppitem_tbl[ppid].size);                           /* 保存参数 */
    
    p_item->flag   = FLAG_DVALID;                                              /* 写状态标记 */
    p_item->size   = s_ppitem_tbl[ppid].size;                                  /* 写数据长度 */
    p_item->chksum = GetItemChksum(p_data, s_ppitem_tbl[ppid].size);           /* 写校验字段 */
    
    s_update_flg = NEED_UPDATE;                                                /* 置标志位等定时器来操作 */
    
    if (now == TRUE) {                                                         /* 需要立即写入 */
        s_delay_cont = DELAY_COUNT - 1;
    }
    
    PPItemChangHdl(ppid);                                                      /* 通知该参数回调函数 */
    
    return TRUE;
}

/**************************************************************************************************
**  函数名称:  PP_RegInformer
**  功能描述:  注册一个参数回调入口
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
BOOLEAN PP_RegInformer(PPITEM_ID_E ppid, void (*fp)(INT16U))
{
    INT16U regid;
    
    SYS_ASSERT(ppid < PPITEM_MAXNO, RETURN_FALSE);
    
    regid = FindFreeRegTbl();
    
    SYS_ASSERT(regid != 0xFF, RETURN_FALSE);
    
    s_ppitem_hdl[regid].status = FLAG_DVALID;
    s_ppitem_hdl[regid].paraid = ppid;
    s_ppitem_hdl[regid].inform = fp;
    
    return TRUE;
}

/**************************************************************************************************
**  函数名称:  PP_WriteAllItems
**  功能描述:  保存所有参数到系统 (不带任何延迟，要慎用!!!)
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
void PP_WriteAllItems(void)
{
    WriteImageToFlash();
}

/**************************************************************************************************
**  函数名称:  PP_ResetAllPara
**  功能描述:  将所有PP参数恢复出厂配置 (立即生效，要慎用!!!)
**  输入参数:  无
**  输出参数:  无
**  返回参数:  无
**************************************************************************************************/
void PP_ResetAllPara(void)
{
    YX_MEMSET(s_ppitem_img, 0, sizeof(s_ppitem_img));
    WriteImageToFlash();
}



